//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#include "Sample.h"

extern bool Test_libNoise ();

/*
=================================================
	constructor
=================================================
*/
	CSampleApplication::CSampleApplication () :
		viewMode(EViewMode::TERRAIN_3D),
		cursorIsHidden(false), vsyncEnabled(false), wireframe(false)
	{
	}
	
/*
=================================================
	destructor
=================================================
*/
	CSampleApplication::~CSampleApplication ()
	{
	}
	
/*
=================================================
	OnInit
=================================================
*/
	void CSampleApplication::OnInit ()
	{
		//Test_libNoise();

		// generate height map
		const uvec2	dim( 1024 );

		texHeight = new GLTexture2D();
		texHeight->Create( gl_tex_iformat::R32F, uvec3(dim) );
		texHeight->SetSampler( GLSamplerSrc::Default_Bilinear_Clamp() );

		const CLGenerator::Texture	textures[] = { CLGenerator::Texture( 0, texHeight ) };
		CLGenerator::Run( "cl/simple_noise.cl", null, null, "Main", svec3(0), svec3( dim ), textures );
		

		// load shader programs
		ShaderLoader::Load( progViewHeight, "shaders/fs_view_height.prg" );
		ShaderLoader::Load( progViewNormals, "shaders/fs_view_normals.prg" );


		// generate normals
		_GenNormals();


		// simple terrain shader
		ShaderLoader::Load( terrainData.prog, "shaders/tess_quad_terrain.prg" );
		terrainData.patchSize = terrainData.prog->GetTessVerticesCount();
		terrainData.unDiffuseMap.GetLocation( *terrainData.prog, "unDiffuseTiles" );
		terrainData.unHeightMap.GetLocation( *terrainData.prog, "unHeightMap" );
		terrainData.unNormalMap.GetLocation( *terrainData.prog, "unNormalMap" );
		terrainData.matMVP.GetLocation( *terrainData.prog, "unMVPMatrix" );
		terrainData.ub.GetLocation( *terrainData.prog, "TerrainUB", 0 );
		

		// terrain shader to view normals
		ShaderLoader::Load( terrainNormalsData.prog, "shaders/tess_quad_terrain_normals.prg" );
		terrainNormalsData.patchSize = terrainNormalsData.prog->GetTessVerticesCount();
		terrainNormalsData.unHeightMap.GetLocation( *terrainNormalsData.prog, "unHeightMap" );
		terrainNormalsData.unNormalMap.GetLocation( *terrainNormalsData.prog, "unNormalMap" );
		terrainNormalsData.matMVP.GetLocation( *terrainNormalsData.prog, "unMVPMatrix" );
		terrainNormalsData.ub.GetLocation( *terrainNormalsData.prog, "TerrainUB", 0 );
		

		// terrain shader to view tesselation level
		ShaderLoader::Load( terrainTesslvlData.prog, "shaders/tess_quad_terrain_tesslvl.prg" );
		GLProgram::uniform_array_t uniforms;
		terrainTesslvlData.prog->GetUniforms( uniforms );
		terrainTesslvlData.patchSize = terrainTesslvlData.prog->GetTessVerticesCount();
		terrainTesslvlData.unHeightMap.GetLocation( *terrainTesslvlData.prog, "unHeightMap" );
		terrainTesslvlData.unNormalMap.GetLocation( *terrainTesslvlData.prog, "unNormalMap" );
		terrainTesslvlData.matMVP.GetLocation( *terrainTesslvlData.prog, "unMVPMatrix" );
		terrainTesslvlData.ub.GetLocation( *terrainTesslvlData.prog, "TerrainUB", 0 );


		// genrate grid
		const uint	grid_size	= 127;
		const uint	patch_size	= terrainData.patchSize;
		CHECK( terrainData.patchSize == terrainNormalsData.patchSize );
		CHECK( terrainData.patchSize == terrainTesslvlData.patchSize );

		grid = new GLMesh();
		MeshGenerator::Grid( *grid, grid_size, 1.0f/grid_size, patch_size );
		

		//* load textures
		const uni_c_string	tex_tiles[] = {
		//	"textures/tiles/h0_a0.jpg",
			"textures/tiles/h1_a0.jpg",
			"textures/tiles/h2_a0.jpg",
			"textures/tiles/h3_a0.jpg",
			"textures/tiles/h4_a0.jpg",
			"textures/tiles/h5_a0.jpg",
			"textures/tiles/h6_a0.jpg",
			"textures/tiles/h7_a0.jpg"
		};
		ImageLoader::Load( texTiles, tex_tiles );
		//texTiles->SetSampler( GLSamplerSrc::Default_Trilinear_Repeat() );
		texTiles->SetSampler( GLSamplerSrc::Default_Anisotropy16x_Repeat() );
		/*/
		_GenTexArray();
		//*/

		// setup render states
		GLRenderState::TColorBuffer	cb;
		cb.bBlending	= false;

		rsTerrain.aColorBuffers[0]		= cb;
		rsTerrain.sDepth.bTest			= true;
		rsTerrain.sDepth.bWrite			= true;
		rsTerrain.sStates.bDepthClamp	= true;

		rsView.aColorBuffers[0]		= cb;
		rsView.sDepth.bTest			= false;
		rsView.sDepth.bWrite		= false;
		rsView.sStates.bDepthClamp	= false;


		// setup camera
		const fvec2 wnd_size = Window()->Descriptor().vSize.To<fvec2>();
		camera.Create( transform_f(), DTR(60.0f), wnd_size.x/wnd_size.y, fvec2(1.0f, 1000.0f) );
	}
	
/*
=================================================
	OnFree
=================================================
*/
	void CSampleApplication::OnFree ()
	{
		progViewHeight	= null;
		progViewNormals	= null;
		texHeight		= null;
		texTiles		= null;
		texNormals		= null;
		grid			= null;

		terrainData.ub.Destroy();
		terrainData.prog = null;

		terrainNormalsData.ub.Destroy();
		terrainNormalsData.prog = null;
		
		terrainTesslvlData.ub.Destroy();
		terrainTesslvlData.prog = null;
	}
	
/*
=================================================
	Draw
=================================================
*/
	void CSampleApplication::Draw ()
	{
		SMAN()->Viewport( Window()->Descriptor().vSize.To<ivec2>() );

		GLDefaultFrameBuffer::ClearColor( fvec4(0.3f, 0.8f, 1.0f, 1.0f) );
		GLDefaultFrameBuffer::ClearDepth( 1.0f );
		
		const fmat4	terrain_scale		= fmat4::Identity();
		const fmat4	terrain_rotation	= fmat4::FromQuat( fquat().RotationX( DTR(90.0f) ) );
		const fmat4	terrain_model_mat	= camera.GetModelMatrix() * terrain_rotation * terrain_scale;
		const fmat4	mvp					= camera.ViewProjMatrix() * terrain_model_mat;

		switch ( viewMode )
		{
			case EViewMode::HEIGHT_MAP :
			{
				rsView.Apply();
				
				texHeight->Bind(0);
				progViewHeight->Bind();
				DrawPoint();
				break;
			}

			case EViewMode::NORMAL_MAP :
			{
				rsView.Apply();
				
				texNormals->Bind(0);
				progViewNormals->Bind();
				DrawPoint();
				break;
			}
			
			case EViewMode::TERRAIN_3D :
			{
				rsTerrain.Apply();

				terrainData.prog->Bind();
				SMAN()->EnableTessellation( terrainData.patchSize != 0, terrainData.patchSize );

				terrainData.ub.Update();
				terrainData.matMVP.SetValue( mvp );

				texHeight->Bind(0);
				terrainData.unHeightMap.SetValue(0);
		
				texNormals->Bind(1);
				terrainData.unNormalMap.SetValue(1);

				texTiles->Bind(2);
				terrainData.unDiffuseMap.SetValue(2);

				grid->DrawAll();
				SMAN()->EnableTessellation( false, 0 );
				break;
			}

			case EViewMode::TERRAIN_3D_NORMALS :
			{
				rsTerrain.Apply();

				terrainNormalsData.prog->Bind();
				SMAN()->EnableTessellation( terrainNormalsData.patchSize != 0, terrainNormalsData.patchSize );

				terrainNormalsData.ub.EditData() = terrainData.ub.ReadData();
				terrainNormalsData.ub.Update();
				terrainNormalsData.matMVP.SetValue( mvp );

				texHeight->Bind(0);
				terrainNormalsData.unHeightMap.SetValue(0);
		
				texNormals->Bind(1);
				terrainNormalsData.unNormalMap.SetValue(1);

				grid->DrawAll();
				SMAN()->EnableTessellation( false, 0 );
				break;
			}

			case EViewMode::TERRAIN_3D_TESSLVL :
			{
				rsTerrain.Apply();

				terrainTesslvlData.prog->Bind();
				SMAN()->EnableTessellation( terrainTesslvlData.patchSize != 0, terrainTesslvlData.patchSize );

				terrainTesslvlData.ub.EditData() = terrainData.ub.ReadData();
				terrainTesslvlData.ub.Update();
				terrainTesslvlData.matMVP.SetValue( mvp );

				texHeight->Bind(0);
				terrainTesslvlData.unHeightMap.SetValue(0);
				
				texNormals->Bind(1);
				terrainTesslvlData.unNormalMap.SetValue(1);

				grid->DrawAll();
				SMAN()->EnableTessellation( false, 0 );
				break;
			}
			
			default :
				WARNING( "!!!" );
				break;
		}
	}
	
/*
=================================================
	Update
=================================================
*/
	void CSampleApplication::Update ()
	{
		const float mouseSens = 0.1f;
		const float moveScale = 0.1f;

		// exit
		if ( Input()->IsKeyClick( e_key::ESCAPE ) )
		{
			Window()->Quit();
			return;
		}
		
		// camera
		{
			fvec3	move;

			move.x += Input()->IsKey( e_key::W );
			move.x -= Input()->IsKey( e_key::S );
			move.y += Input()->IsKey( e_key::D );
			move.y -= Input()->IsKey( e_key::A );
			move.z += Input()->IsKey( e_key::SPACE );
			move.z -= Input()->IsKey( e_key::L_SHIFT );

			camera.MoveFPSFree( move * moveScale );
		}

		if ( Input()->IsKey( e_key::M_LEFT ) )
		{
			camera.RotateFPS( Input()->GetMouseDelta() * mouseSens );
		
			Input()->CursorToCenter();

			if ( not cursorIsHidden )
			{
				cursorIsHidden = true;
				Window()->ShowCursor( false );
			}
		}
		else
		if ( cursorIsHidden )
		{
			cursorIsHidden = false;
			Window()->ShowCursor( true );
		}

		// terrain height
		if ( Input()->IsKeyClick( e_key::R_BRACKET ) )	terrainData.ub.EditData().fHeightScale += TerrainUB::HeightScaleStep();
		if ( Input()->IsKeyClick( e_key::L_BRACKET ) )	terrainData.ub.EditData().fHeightScale -= TerrainUB::HeightScaleStep();

		// detail level
		if ( Input()->IsKeyClick( e_key::PERIOD ) )		terrainData.ub.EditData().fDetailLevel += TerrainUB::DetailLevelStep();
		if ( Input()->IsKeyClick( e_key::COMMA ) )		terrainData.ub.EditData().fDetailLevel -= TerrainUB::DetailLevelStep();
		
		// tessellation level
		if ( Input()->IsKeyClick( e_key::PLUS ) )		terrainData.ub.EditData().fMaxTessLevel += TerrainUB::TessLevelStep();
		if ( Input()->IsKeyClick( e_key::MINUS ) )		terrainData.ub.EditData().fMaxTessLevel -= TerrainUB::TessLevelStep();
		
		// grid scale
		if ( Input()->IsKeyClick( e_key::_0 ) )			terrainData.ub.EditData().fGridScale += TerrainUB::GridScaleStep();
		if ( Input()->IsKeyClick( e_key::_9 ) )			terrainData.ub.EditData().fGridScale -= TerrainUB::GridScaleStep();

		// regenerate normals
		if ( Input()->IsKeyClick( e_key::G ) )			_GenNormals();

		// vsync
		if ( Input()->IsKeyClick( e_key::V ) )
		{
			vsyncEnabled = not vsyncEnabled;
			Window()->SetVSync( vsyncEnabled );
		}

		// wireframe
		if ( Input()->IsKeyClick( e_key::P ) )
		{
			wireframe = not wireframe;
			rsTerrain.ePolygon = wireframe ? gl_polygon_mode::LINE : gl_polygon_mode::FILL;
		}

		// view modes
		if ( Input()->IsKeyClick( e_key::F1 ) )		viewMode = EViewMode::TERRAIN_3D;
		if ( Input()->IsKeyClick( e_key::F2 ) )		viewMode = EViewMode::TERRAIN_3D_NORMALS;
		if ( Input()->IsKeyClick( e_key::F3 ) )		viewMode = EViewMode::TERRAIN_3D_TESSLVL;
		if ( Input()->IsKeyClick( e_key::F4 ) )		viewMode = EViewMode::HEIGHT_MAP;
		if ( Input()->IsKeyClick( e_key::F5 ) )		viewMode = EViewMode::NORMAL_MAP;
	}
	
/*
=================================================
	OnResize
=================================================
*/
	void CSampleApplication::OnResize ()
	{
		const fvec2 wnd_size = Window()->Descriptor().vSize.To<fvec2>();
		camera.Resize( DTR(60.0f), wnd_size.x/wnd_size.y );
		SMAN()->Viewport( Window()->Descriptor().vSize.To<ivec2>() );
	}
	
/*
=================================================
	_GenNormals
=================================================
*/
	void CSampleApplication::_GenNormals ()
	{
		if ( texNormals.IsNull() )
		{
			texNormals = new GLTexture2D();
			texNormals->SetImage( gl_tex_iformat::RGB16F, texHeight->Dimension(), gl_pixel_format::RGB, gl_pixel::FLOAT, null );
			texNormals->SetSampler( GLSamplerSrc::Default_Bilinear_Clamp() );
		}

		GLProgramPtr		gen_normals = new GLProgram();
		ShaderLoader::Load( gen_normals, "shaders/gen_normals.prg" );
		sampler2D_t			height_map( *gen_normals, "unHeightMap" );
		GLUniform<float>	height_scale( *gen_normals, "unHeightScale" );
		GLFrameBuffer		fb;
		
		fb.Create();
		fb.Attach( *texNormals, gl_fbo_attach::COLOR_0 );
		fb.Bind();
		SMAN()->Viewport( texNormals->Dimension().xy().To<ivec2>() );

		gen_normals->Bind();

		texHeight->Bind(0);
		height_map.SetValue(0);
		height_scale.SetValue( Abs( terrainData.ub.ReadData().fHeightScale / texNormals->Dimension().Max() ) );

		DrawPoint();
		fb.Unbind();
	}
	
/*
=================================================
	_GenTexArray
=================================================
*/
	void CSampleApplication::_GenTexArray ()
	{
		const fvec4 colors[] = {
			fvec4(0.5f, 0.5f, 0.5f, 1.0f),
			fvec4(1.0f, 0.0f, 0.0f, 1.0f),
			fvec4(0.0f, 1.0f, 0.0f, 1.0f),
			fvec4(0.0f, 0.0f, 1.0f, 1.0f)
		};

		texTiles = new GLTexture2DArray();
		texTiles->SetImage( gl_tex_iformat::RGBA8, uvec3(128, 128, CountOf(colors)), gl_pixel_format::RGBA, gl_pixel::UBYTE, null, 0 );

		for (uint i = 0; i < texTiles->Dimension().z; ++i)
		{
			GLFrameBuffer	fb;
			fb.Create();
			fb.AttachLayer( *texTiles, gl_fbo_attach::COLOR_0, i, 0 );
			fb.Bind();
			SMAN()->Viewport( texTiles->Dimension().xy().To<ivec2>() );
			fb.ClearColor( colors[i] );
			fb.Unbind();
		}
		
		texTiles->GenerateMipmaps();
		texTiles->SetSampler( GLSamplerSrc::Default_Anisotropy16x_Repeat() );
	}

/*
=================================================
	Run
=================================================
*/
	bool CSampleApplication::Run ()
	{
		CSampleApplication	app;

		return
			app._Run( "1-TerrainGen/data",
					  CWindow::TDescriptor( uvec2(640,480), ivec2(), e_wnd_style::DEF_WINDOWED, 0, false ),
					  GLWindow::TGLDescriptor( e_glinit_flags::DEFAULT_CORE, 32, 24, 0, 8, 4, true, null ),
					  EInitFlags::USE_OPENCL );
	}
